﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.DirectoryServices.AccountManagement;
using System.Threading.Tasks;
using System.Windows.Data;
using System.Windows.Threading;
using Red.Launcher.Utilities;
using static System.Windows.Application;
using Database = Red.Launcher.ViewModels.Workspaces.Database;
using Sql = Red.Launcher.ViewModels.Workspaces.Database.Sql;


namespace Red.Launcher.ActiveDirectory
{
    public class Users
    {
        #region Public Types

        public class User
        {
            public bool Add { get; set; }
            public bool ExistsInDatabase { get; set; }
            public UserPrincipal Principal { get; set; }

            public override string ToString()
            {
                return Principal.ToString();
            }
        }

        #endregion Public Types

        #region Public Properties

        public ObservableCollection<User> Raw { get; private set; } = new ObservableCollection<User>();
        public ICollectionView View { get; private set; }
        public List<string> Existing { get; private set; } = new List<string>();

        #endregion Public Properties

        #region Private Properties

        private AsyncFetcher<UserPrincipal> Fetcher { get; set; } = new AsyncFetcher<UserPrincipal>();

        #endregion Private Properties

        #region C-Tor

        public Users()
        {
            View = CollectionViewSource.GetDefaultView( Raw );
        }

        #endregion C-Tor

        #region Public Methods

        public void Clear()
        {
            Existing.Clear();
            Raw.Clear();
        }

        public async Task Fetch()
        {
            await FetchExisting();

            await FetchActiveDirectory();

            await WrapActiveDirectory();
        }

        public async Task<bool> Import()
        {
            return await Task.Run( async () => { return await ImportInternal(); } );
        }

        #endregion Public Methods

        #region Private Methods

        private async Task FetchActiveDirectory()
        {
            await Fetcher.Fetch();
        }

        private async Task FetchExisting()
        {
            Sql.Select.Table users = Sql.Select.CreateTable( Database.User.METADATA.Table, "u" );

            Sql.Select query = new Sql.Select();
            query.Sources.Add( users );
            query.AddColumn( "username_win", users, "name" );

            await DatabaseHelper.FetchAsyncSafe( query.Sql, Settings.DatabaseConnectionString, FetchExistingCallback );
        }

        private async Task WrapActiveDirectory()
        {
            await Task.Run( (Action)WrapActiveDirectoryInternal );
        }

        private void WrapActiveDirectoryInternal()
        {
            foreach ( UserPrincipal principal in Fetcher.Items )
            {
                User toAdd = new User()
                {
                    ExistsInDatabase = Existing.Exists( user => user == principal.UserPrincipalName ),
                    Principal = principal
                };

                Current.Dispatcher.BeginInvoke( DispatcherPriority.Normal, (Action<User>)AddUser, toAdd );
            }
        }

        private async Task<bool> ImportInternal()
        {
            Sql.Transaction transaction = new Sql.Transaction();

            foreach ( User potentialUser in Raw )
            {
                if ( potentialUser.Add )
                {
                    Database.User databaseUser = new Database.User()
                    {
                        Name = potentialUser.Principal.UserPrincipalName
                    };

                    Sql.Insert insert = new Sql.Insert( databaseUser );
                    transaction.Add( insert );
                }
            }

            return await DatabaseHelper.ExecuteAsyncSafe( transaction.Sql, Settings.DatabaseConnectionString, transaction.Parameters ) == DatabaseHelper.Result.Success;
        }

        #endregion Private Methods

        #region Event Handlers

        private void FetchExistingCallback( Dictionary<string, object> row )
        {
            Existing.Add( (string)row[ "name" ] );
        }

        private void AddUser( User user )
        {
            Raw.Add( user );
        }

        #endregion Event Handlers
    }
}
